package com.cctc.rds.nrpc.core.protocol.message.impl;

import com.cctc.rds.nrpc.api.rpc.RpcResponse;
import com.cctc.rds.nrpc.core.protocol.message.MessageType;
import com.cctc.rds.nrpc.core.util.JsonUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RpcResponseMessage extends AbstractMessage {
    private String txid;
    @JsonIgnore
    private transient RpcResponse rpcResponse;
    private Map<String, String> body;

    public RpcResponseMessage(RpcResponse rpcResponse) {
        setRpcResponse(rpcResponse);
    }

    @Override
    public Byte getType() {
        return MessageType.RESPONSE.getValue();
    }

    public void setRpcResponse(RpcResponse rpcResponse) {
        this.rpcResponse = rpcResponse;
        this.body = new HashMap<>();
        if (rpcResponse.getResult() != null) {
            String resultType = rpcResponse.getResult().getClass().getName();
            if (rpcResponse.getResult() instanceof List && ((List<?>) rpcResponse.getResult()).size() > 0) {
                resultType = resultType + ";" + ((List<?>) rpcResponse.getResult()).get(0).getClass().getName();
            }
            this.body.put("resultType", resultType);
            this.body.put("result", JsonUtils.toJson(rpcResponse.getResult()));
        }
        if (rpcResponse.getException() != null) {
            Throwable cause = getCause(rpcResponse.getException());
            this.body.put("exceptionType", cause.getClass().getName());
            this.body.put("exception", JsonUtils.toJson(cause));
        }
    }

    private static Throwable getCause(Throwable e) {
        Throwable cause = e;
        while (cause.getCause() != null) cause = cause.getCause();
        return cause;
    }

    public RpcResponse getRpcResponse() {
        if (rpcResponse == null && this.body != null) {
            rpcResponse = RpcResponse.builder().build();
            try {
                if (this.body.containsKey("result")) {
                    String resultType = this.body.get("resultType");
                    Class<?> tClass, pClass = null;
                    if (!resultType.contains(";")) {
                        tClass = Class.forName(resultType);
                    } else {
                        String[] resultTypes = resultType.split(";");
                        tClass = Class.forName(resultTypes[0]);
                        pClass = Class.forName(resultTypes[1]);
                    }
                    Object result = null;
                    if (pClass == null) {
                        result = JsonUtils.fromJson(this.body.get("result"), tClass);
                    } else {
                        result = JsonUtils.fromJsonList(this.body.get("result"), pClass);
                    }
                    rpcResponse.setResult(result);
                }
                if (this.body.containsKey("error")) {
                    Class<Exception> eClass = (Class<Exception>) Class.forName(this.body.get("exceptionType"));
                    rpcResponse.setException(JsonUtils.fromJson(this.body.get("exception"), eClass));
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        return rpcResponse;
    }

}
